﻿// Sequin.WebLogs.ParsedUri
//
// (c) 2011 Arthur Pitman
//
// Part of the Sequin Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License (Version 2.1) as published by the Free Software Foundation.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License (Version 2.1) along with this library; if not, write to the 
// Free Software Foundation, Inc., 
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
// or see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

namespace Sequin.WebLogs
{
	/// <summary>
	/// Represents a URI parsed into its various elements
	/// </summary>
	public class ParsedUri
	{
		/// <summary>
		/// The original URI used to create this <see cref="ParsedUri"/>
		/// </summary>
		public string Uri { get; protected set; }

		/// <summary>
		/// The address part of the URI (before any ?)
		/// </summary>
		public string Address { get; protected set; }

		/// <summary>
		/// The arguments part of the URI (after any ?)
		/// </summary>       
		public string Arguments { get; protected set; }

		/// <summary>
		/// The set of assignments created from the arguments (seperated by &amp;)
		/// </summary>       
		public string[] Assignments { get; protected set; }

		/// <summary>
		/// The assignments as a <see cref="Dictionary{K,V}"/>
		/// </summary>
		public Dictionary<string, string> ArgumentDictionary { get; protected set; }

		/// <summary>
		/// Flag indicating if the URI ended with a question mark but had no arguments
		/// </summary>
		public bool EndsWithQuestionMark { get; protected set; }

		/// <summary>
		/// Flag if the URI was marked a on-site, i.e. started with a $
		/// </summary>
		public bool OnSite { get; protected set; }

		/// <summary>
		/// Splitter to split up arguments
		/// </summary>
		protected static char[] argSplitter = new char[] { '&' };

		/// <summary>
		/// Splitter to split names from their values
		/// </summary>
		protected static char[] varSplitter = new char[] { '=' };

		/// <summary>
		/// Initializes a new <see cref="ParsedUri"/> instance
		/// </summary>
		/// <param name="uri">The <see cref="string"/> representing the URI</param>
		public ParsedUri(string uri)
		{
			Uri = uri;
			ArgumentDictionary = new Dictionary<string, string>();
			OnSite = ((uri.Length > 0) && (uri[0] == '$'));

			// try to find where to break the URI
			int breakPoint = uri.IndexOf("?");
			if (breakPoint == -1)
			{
				// if no question mark was found, check to see if the URI was wrongly encoded
				int newBreakPoint = uri.IndexOf("%3f", StringComparison.InvariantCultureIgnoreCase);
				if (newBreakPoint != -1)
				{
					Uri = uri = HttpUtility.UrlDecode(uri);
					breakPoint = uri.IndexOf("?");
				}
			}

			// break the URI
			if (breakPoint == -1)
			{
				Address = uri;
				EndsWithQuestionMark = false;
				Assignments = new string[0];
				Arguments = "";
			}
			else if (breakPoint == (uri.Length - 1))
			{
				Address = uri.Substring(0, breakPoint);
				EndsWithQuestionMark = true;
				Assignments = new string[0];
				Arguments = "";
			}
			else
			{
				Address = uri.Substring(0, breakPoint);
				EndsWithQuestionMark = false;
				Arguments = uri.Substring(breakPoint + 1);

				// process arguments
				Assignments = Arguments.Split(argSplitter, StringSplitOptions.RemoveEmptyEntries);
				foreach (string argElement in Assignments)
				{
					string[] varElements = argElement.Split(varSplitter);
					string name = HttpUtility.UrlDecode(varElements[0]);
					string value = "";
					if (varElements.Length > 1)
						value = HttpUtility.UrlDecode(varElements[1]);
					if (ArgumentDictionary.ContainsKey(name))
					{
						if (value.Length > 0)
							ArgumentDictionary[name] = value;
					}
					else
					{
						ArgumentDictionary.Add(name, value);
					}
				}
			}
		}

		/// <summary>
		/// Checks to see if the URI includes a particular argument name-value pair
		/// </summary>
		/// <param name="varName">The variable name to look for</param>
		/// <param name="value">The associated value to look for</param>
		/// <returns><c>true</c> if the pair was found</returns>
		public bool HasArgumentValue(string varName, string value)
		{
			return ArgumentDictionary.Contains(new KeyValuePair<string, string>(varName, value));
		}

		/// <summary>
		/// Replaces meta-ampersand characters with the actual character
		/// </summary>
		/// <param name="uri">The uri <see cref="string"/> to process</param>
		/// <returns>The processed <see cref="string"/></returns>
		public static string RemoveAmps(string uri)
		{
			return uri.Replace("&amp;", "&");
		}

		/// <summary>
		/// Replaces the first ampersand characters with the question mark character
		/// </summary>
		/// <param name="uri">The uri <see cref="string"/> to process</param>
		/// <returns>The processed <see cref="string"/></returns>
		public static string RepairQuestionMark(string uri)
		{
			char[] chars = uri.ToCharArray();
			for (int i = 0; i < chars.Length; i++)
			{
				var ch = chars[i];			 
				if (ch == '?')
					return uri;
				else if (ch == '&')
				{
					chars[i] = '?';
					return new string(chars);
				}
			}
			return uri;
		}

		/// <summary>
		/// Decodes a URL encoded string, optionally repetitively
		/// </summary>
		/// <param name="input">The string to decode</param>
		/// <param name="maxIterations">The maximum number of iterations to appy, 0 for no limit</param>
		/// <returns>The processed string</returns>
		public static string DecodeString(string input, int maxIterations = 1)
		{
			for (int i = 0; (i < maxIterations) || (maxIterations == 0); i++)
			{
				var newInput = HttpUtility.UrlDecode(input);

				if (newInput == input)
					break;
				input = newInput;
			}
			return input;
		}

	}

}
